# -*- mode: python; coding: utf-8; -*-
##--------------------------------------------------------------------------##
##
## Copyright (C) 1998-2003 Markus Oberhumer <markus@oberhumer.com>
## Copyright (C) 2003 Mt. Hood Playing Card Co.
## Copyright (C) 2005-2010 Skomoroh <skomoroh@users.sourceforge.net>
## Copyright (C) 2010-2013 Jeremy Austin-Bardo <tjaustinbardo@gmail.com>
##
## This program is free software: you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation, either version 3 of the License.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program.  If not, see <http://www.gnu.org/licenses/>.
##
##--------------------------------------------------------------------------##
"""
Methods for all toolkits
========================

Game Menu:
 Provide stats and details about current and past games.

Assist Menu:
  Provide interactive help for current game. Start a demo game.

Help menu:
  Provide written help for interface and games. Show credits and about game.
"""
__docformat__ = 'restructuredtext en'
#n_ = lambda x: x
#_ = lambda x: x

from .dialog import Dialog
from .tooltip import PileToolTips
from ..gamelib import state
from ..utillib.misc import openURL
from ..persistance import NOTE_DB
from ..settings import CONF_OPT, PACKAGE_URL, TITLE
from ..statdb import STAT_DB
from ..utillib.loadhelp import load_help


class GameMenuMixin(object):
    """
    Build the Game menu.
    """

    def connect_game(self, game):
        """
        Connect menu with currnet game.

        Overide to set comment and pause state. Cell next method in order.

        .. \connect_game(game:Game) -> None
        """
        game_name = self.game['name']
        self._set_option('comment', bool(NOTE_DB.get(game_name)))
        self._set_option('pause', game.is_pause)

        ancestor = super(GameMenuMixin, self)
        if hasattr(ancestor, 'connect_game'):
            ancestor.connect_game(game)

    def _add_game_menu(self, menu):
        """
        Create game menu.
          - [command] Deal cards
          - [command] Auto drop
          - [command] Shuffle tiles
          - [command] Pause
          - [command] Status...
          - [command] Comments...
          - [command] Statistics...
          - [command] Log...
          - [command] Demo statistics

        .. \_add_assist_menu(menu:Menu) -> None
        """
        menu.add_command(label=n_('&Deal cards'),
                         accelerator='D',
                         command=self._do_deal_move)
        menu.add_command(label=n_('&Auto drop'),
                         accelerator='A',
                         command=self._do_drop_move)
        menu.add_command(label=n_('Shu&ffle tiles'),
                         accelerator='F',
                         command=self._do_shuffle_tiles)
        menu.add_checkbutton(label=n_('&Pause'),
                             variable=self._get_variable('pause'),
                             accelerator='P',
                             command=self._do_pause_game)
        menu.add_separator()
        menu.add_checkbutton(label=n_('&Comments...'),
                             variable=self._get_variable('comment'),
                             command=self._edit_game_comment)
        menu.add_separator()
        menu.add_command(label=n_('&Statistics...'),
                         accelerator=self._meta_mkey.format('T'),
                         command=lambda m=101: self._view_game_stat(mode=m))
        menu.add_command(label=n_('Log...'),
                         command=lambda m=103: self._view_game_stat(mode=m))
        menu.add_separator()
        menu.add_command(label=n_('D&emo statistics'),
                         command=lambda m=1101: self._view_game_stat(mode=m))

    def create_keybindings(self):
        """
        Bind assist commands to a key or meta and key combo.

        Set key combo to show help. Call next method in order.
          - <p> pause game.
          - <*meta*-t> view game stats.
          - <f> mix-up mahjongg tiles.
          - <a> drop card on foundation - flip up config option.
          - <*meta*-a> drop card on foundation - force flip up.
          - <d> deal card from talon.
          - <l> drop card on foundation - flip up config option.
          - <*meta*-l> drop card on foundation - force flip up.
          - <j> deal card from talon.
          - <pause> pause game.
          - <space> deal card from talon.

         .. \create_keybindings() -> None
        """
        self._bind_keypress(None, 'p',
                            lambda e: self._do_pause_game())
        self._bind_keypress(self._meta_bkey, 't',
                            lambda e, m=101: self._view_game_stat(mode=m))
        self._bind_keypress(None, 'f',
                            lambda e: self._do_shuffle_move())
        self._bind_keypress(None, 'a',
                            lambda e: self._do_drop_move())
        self._bind_keypress(self._meta_bkey, 'a',
                            lambda e, f=True: self._do_drop_move(f))
        self._bind_keypress(None, 'd',
                            lambda e:self._do_deal_move())
        self._bind_keypress(None, 'l',
                            lambda e: self._do_drop_move())
        self._bind_keypress(self._meta_bkey, 'l',
                            lambda e: lambda e, f=True: self._do_drop_move(f))
        self._bind_keypress(None, 'j',
                            lambda e: self._do_deal_move())
        self._bind_keypress(None, 'Pause',
                            lambda e: self._do_pause_game())
        self._bind_keypress(None, 'space',
                            lambda e: self._do_deal_move())

        ancestor = super(GameMenuMixin, self)
        if hasattr(ancestor, 'create_keybindings'):
            ancestor.create_keybindings()

    @state.game_connect
    def update_menu_state(self, clear=False):
        """
        Update menu item enable state.

        Overide to update state for game menu command for deal cards, auto drop,
        shuffle tiles plays. Update state for game menu command for pausing
        a game. Call next method in order.

        .. update_menu_state(clear:bool=False) -> None
        """
        from .console import TOOL_BAR

        game = self.game

        if clear:
            deal = autodrop = shuffle = pause = False
        else:
            sg_ = self.game.s
            deal = bool(sg_['talon'] and sg_['talon'].can_deal_cards())
            autodrop = bool(game._autopiles()[1] and sg_['foundations'])
            shuffle = bool(CONF_OPT['shuffle'] and
                           hasattr(game, '_shuffle_tile'))
            pause = bool(not self.game.is_finish)

        self.set_state(deal, 'game.dealcards')
        self.set_state(autodrop, 'game.autodrop')
        self.set_state(shuffle, 'game.shuffletiles')
        self.set_state(pause, 'game.pause')

        TOOL_BAR.update_region_state(autodrop=autodrop, shuffle=shuffle,
                                     pause=pause)

        ancestor = super(GameMenuMixin, self)
        if hasattr(ancestor, 'update_menu_state'):
            ancestor.update_menu_state()

    @state.game_connect
    def _do_pause_game(self):
        """
        Pause/Unpause game.

        Call game do pause then update menu options.

        .. \_do_pause_game() -> None
        """
        self.game._timer_pause(not self.game.is_pause)
        self._set_option('pause', self.game.is_pause)

    @state.cancel_drag(True)
    def _do_deal_move(self):
        """
        Deal next card from Talon.

        Call game play deal move.

        .. \_do_deal_move() -> None
        """
        self.game.talon_deal()

    @state.cancel_drag(True)
    def _do_drop_move(self, autofaceup=False):
        """
        Auto drop card on foundation.

        Call game auto play method to drop playable cards on foundations.

        .. \_do_drop_move(autofaceup:bool=False) -> None
        """
        self.game.auto_play(flip=autofaceup)

    @state.cancel_drag(True)
    def _do_shuffle_tiles(self):
        """
        Shuffle Mahjongg tile in place.

        Call game shuffle tile when game uses Mahjongg tiles to remix tiles.

        .. \_do_shuffle_tiles() -> None
        """
        if not hasattr(self.game, '_shuffle_tile'):
            return
        self.game._shuffle_tile()

    @state.cancel_drag(False)
    def _edit_game_comment(self):
        """
        Edit comments for game.

        Call dialog comment to add a comment for game then update menu options.

        .. \_edit_game_comment() -> None
        """
        Dialog.comment(self.game['name'], '#{0!s}'.format(self.game.random))
        self._set_option('comment', bool(NOTE_DB.get(self.game['name'])))

    @state.cancel_drag(False)
    def _view_game_stat(self, mode=101):
        """
        View game stat overview or log.

        Call stat viewer or stat log based on given mode. Optionally write stats
        or log; reset player for all or current games; or start a selected game
        based on mode returned from dialog.

        .. \_view_game_stat(mode:int) -> None
        """
        from .console import update_console
        from ._dlg_stats import (StatsViewer as _StatsViewer,
                         StatsDialog as _StatsDialog,)

        game = self.game
        player = _('Demo') if mode > 1000 else CONF_OPT['player']
        mode = mode % 1000

        if mode == 101:
            dialog = _StatsViewer(self.top, player, game['id'])
        if mode == 103:
            dialog = _StatsDialog(self.top, player, game['id'])

        if dialog.mode in (202, 203, 204):
            from ..statform import save_game_stats
            # 202 - from StatsViewer - (1) Full Summary by <save summary>
            # 203 - from StatsDialog - (0) Full Log by <save log>
            # 204 - from StatsDialog - (1) Sessions Log by <save log>
            save_game_stats(player, dialog.mode)

        if dialog.mode in (301, 302):
            type_ = 'all' if dialog.mode == 301 else self.game['name']
            check = Dialog.confirm(text=_('Reset {} game stats and logs for {}?'
                                         ).format(type_, player))
            # 301 - from StatsViewer - (1) Full Summary by <reset>
            # 301 - from StatsDialog - (0) Full Log by <reset>
            if mode == 301 and check:
                STAT_DB.reset(player)
            # 302 - from StatsViewer - (2) Game Summary by <reset>
            # 302 - from StatsDialog - (1) Sessions Log by <reset>
            if mode == 302 and check:
                STAT_DB.reset(player, self.game['id'])
            update_console(stats=STAT_DB.basic(player, self.game['id']))

        if dialog.mode == 401:
            # 401 - from StatsViewer - (1) Full Summary by <select game>
            self._new_game_by_id(dialog.gameid)

    def _new_game_by_id(self, id_, random=None):
        """
        Start a new game by id.

        Stub satisfied by FileMenuMixin.

        .. \_new_game_by_id(id_:int, random:Random=None):
        """
        raise NotImplementedError


class AssistMenuMixin(object):
    """
    Build the assist menu.
    """

    def _add_assist_menu(self, menu):
        """
        Create Assist menu.
          - [command] Hint
          - [command] Highlight p&iles
          - [command] Demo
          - [command] Demo (&all games)
          - [command] Piles description

        .. \_add_assist_menu(menu:Menu) -> None
        """
        menu.add_command(label=n_('&Hint'),
                         accelerator='H',
                         command=self._show_pile_hint)
        menu.add_command(label=n_('Highlight p&iles'),
                         accelerator='I',
                         command=self._show_pile_sequence)
        menu.add_separator()
        menu.add_command(label=n_('&Demo'),
                         accelerator=self._meta_mkey.format('D'),
                         command=self._demo_game)
        menu.add_command(label=n_('Demo (&all games)'),
                         command=lambda m=True: self._demo_game(m))
        menu.add_separator()
        menu.add_command(label=n_('&Piles description'),
                         command=self._show_pile_help,
                         accelerator='F2')

    def create_keybindings(self):
        """
        Bind assist commands to a key or meta and key combo.

        Set key combo to show help. Call next method in order.
          - <h> show available pile hint.
          - <*meta*-h> show available pile hint w/ debug.
          - <i> show available pile sequence.
          - <*meta*-d> do demo game.
          - <F2> show available  pile help.

        .. create_keybindings() -> None
        """
        self._bind_keypress(None, 'h',
                            lambda e: self._show_pile_hint())
        self._bind_keypress(None, 'i',
                            lambda e: self._show_pile_sequence())
        self._bind_keypress(self._meta_bkey, 'd',
                            lambda e: self._demo_game())
        self._bind_keypress(None, 'F2',
                            lambda e: self._show_pile_help())

        ancestor = super(AssistMenuMixin, self)
        if hasattr(ancestor, 'create_keybindings'):
            ancestor.create_keybindings()

    @state.game_connect
    def update_menu_state(self, clear=False):
        """
        Update menu item enable state.

        Overide to update state for assist menu command for pile hint and
        sequence highlight. Update state for assist menu command for demo and
        demo all games. Call next method in order.

        .. update_menu_state(clear:bool=False) -> None
        """
        game = self.game
        if clear:
            hint = demo = mark_piles = False
        else:
            hint = demo = False
            if game._hint_ is not None:
                hint = CONF_OPT['hint']
                demo = True
                mark_piles = bool(CONF_OPT['mark_piles'] and
                                         game._highlight_piles)

        self.set_state(hint, 'assist.hint')
        self.set_state(mark_piles, 'assist.highlightpiles')
        self.set_state(demo, 'assist.demo')
        self.set_state(demo, 'assist.demoallgames')

        ancestor = super(AssistMenuMixin, self)
        if hasattr(ancestor, 'update_menu_state'):
            ancestor.update_menu_state()

    @state.cancel_drag(True)
    def _show_pile_help(self):
        """
        Show tip for unique piles.

        Call PileToolTips to create tip over piles that explain what builds on
        and what plays off.

        .. _show_pile_help() -> None
        """
        PileToolTips(self.game)
    @state.cancel_drag(True)
    def _show_pile_sequence(self):
        """
        Show piles that will build from piles that play by highlighting card.

        .. \_show_available() -> None
        """
        highlight_piles = self.game._highlight_piles  # seq(Pile, Length)
        if not CONF_OPT['mark_piles'] or len(highlight_piles) == 0:
            return

        color = CONF_OPT['colors']['piles']
        info = []
        for stack_info in highlight_piles:
            for stack in stack_info[0]:
                pile = stack.SEQUENCE
                if pile and len(pile) >= stack_info[1]:
                    info += [(stack, pile[0], pile[-1], color,)]

        self.game.view_match('piles', info)

    @state.cancel_drag(True)
    def _show_pile_hint(self):
        """
        Show game hint with arrow from play pile to build pile.

        .. \_show_pile_hint() -> None
        """
        self.game.view_hint()

    @state.cancel_drag(True)
    def _demo_game(self, mixed=False):
        """
        Begin demo of game.

        Confirm demo when game in play without prior demo. Call game do demo
        method to begin using game move hints.

        .. \_demo_game(mixed:bool=False) -> None
        """
        game = self.game
        if game.changed() and game.move_demo:
            if not Dialog.confirm(text=_('Start demo game?')):
                return
        game.demo_start(mixed=mixed)


class HelpMenuMixin(object):
    """
    Build the help menu.
    """

    def _add_help_menu(self, menu):
        """
        Create Help menu.

          - [command] Contents
          - [command] How to play
          - [command] Rules for this game
          - [command] credits
          - [command] About

        .. \_add_help_menu(menu:Menu) -> None
        """
        menu.add_command(label=n_('&Contents'),
                         accelerator=self._meta_mkey.format('F1'),
                         command=self._help)
        menu.add_command(label=n_('&How to play'),
                         command=self._help_program)
        menu.add_command(label=n_('&Rules for this game'),
                         accelerator='F1',
                         command=self._help_gameplay)
        menu.add_separator()
        menu.add_command(label=n_('_{} {}...').format(_('&About'), TITLE),
                             command=self._help_about)

    def create_keybindings(self):
        """
        Bind commands to a key or meta and key combo.

        Set key combo to show help. Call next method in order.
          - <*meta*-F1> show help index page.
          - <F1> show game specific help.

        .. create_keybindings() -> None
        """
        self._bind_keypress(self._meta_bkey, 'F1', self._help)
        self._bind_keypress(None, 'F1', self._help_gameplay)

        ancestor = super(HelpMenuMixin, self)
        if hasattr(ancestor, 'create_keybindings'):
            ancestor.create_keybindings()

    @state.game_connect
    def update_menu_state(self, clear=False):
        """
        Update menu item enable state.

        Overide to update state for help menu command for game help and toolbar
        button for game help. Call next method in order.

        .. update_menu_state(clear:bool=False) -> None
        """
        from .console import TOOL_BAR

        if clear:
            rules = False
        else:
            rules = bool(self.game['rules_filename'] is not None)

        self.set_state(rules, 'help.rulesforthisgame')
        TOOL_BAR.update_region_state(rules=rules)

        ancestor = super(HelpMenuMixin, self)
        if hasattr(ancestor, 'update_menu_state'):
            ancestor.update_menu_state()

    @state.cancel_drag(False)
    def _help(self):
        """
        Show help index page.

        .. \_help() -> None
        """
        load_help('index')

    @state.cancel_drag(False)
    def _help_program(self):
        """
        Show general help about the program.

        .. \_help_program() -> None
        """
        load_help('howtoplay')

    @state.cancel_drag(False)
    def _help_gameplay(self):
        """
        Show specific help about current game.

        .. \_help_program() -> None
        """
        if self.game['rules_filename'] is not None:
            load_help(self.game['rules_filename'])

    @state.cancel_drag(False)
    def _help_website(self):
        """
        Launch website for program.

        .. \_help_website() -> None
        """
        openURL(PACKAGE_URL)

    @state.cancel_drag(False)
    def _help_about(self):
        """
        Show dialog about program.

        .. \_help_about() -> None
        """
        Dialog.about()